package cn.com.bluemoon.daps.common.datascope.aop;

import cn.com.bluemoon.daps.common.constant.DapRoleEnum;
import cn.com.bluemoon.daps.common.datascope.AuthRole;
import cn.com.bluemoon.daps.common.domain.UserInfoHolder;
import cn.com.bluemoon.daps.common.exception.DapException;
import cn.com.bluemoon.daps.common.exception.DapThrowException;
import cn.com.bluemoon.daps.common.toolkit.BmAssetUtils;
import cn.com.bluemoon.daps.domp.api.IDompService;
import cn.com.bluemoon.daps.domp.api.param.RoleDto;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 角色权限控制切面
 *
 * @author Jarod.Kong
 */
@Aspect
@Component
@Slf4j
@Order(value = Integer.MAX_VALUE - 5)
@ConditionalOnProperty(
        value = {"bm.role.http.enabled"},
        matchIfMissing = true
)
public class AuthRoleAspect {
    private static final ConcurrentHashMap<Class<? extends AuthAccessService>, AuthAccessService> authAccess = new ConcurrentHashMap<>();
    @Autowired
    private IDompService dompService;

    @Pointcut("@annotation(cn.com.bluemoon.daps.common.datascope.AuthRole)")
    public void authRole() {
    }

    @Around("authRole()")
    public Object handlerControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
        Method requestMethod = ((MethodSignature) pjp.getSignature()).getMethod();
        AuthRole authRole = requestMethod.getAnnotation(AuthRole.class);
        if (authRole != null) {
            DapRoleEnum[] roleEnums = authRole.roleAuth();
            BmAssetUtils.isTrue(roleEnums.length > 0, () -> new DapException("请指定@AuthRole#value角色名称"));
            UserInfoHolder.UserInfo userInfo = UserInfoHolder.getUserInfo().orElseThrow(() -> new DapException("获取当前用户信息失败"));
            boolean hasRoleAuth = dompService.hasSysRoleByAuthInfos(userInfo.getToken(), userInfo.getAccount(), Arrays.stream(roleEnums).flatMap(r -> Arrays.stream(r.getRoleNames())).collect(Collectors.toSet()));
            // 附加自定义权限业务逻辑
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest servletRequest = requestAttributes != null ? requestAttributes.getRequest() : null;
            customRoleAccess(userInfo, authRole, hasRoleAuth, pjp, servletRequest);
        }
        return pjp.proceed();
    }

    private void customRoleAccess(UserInfoHolder.UserInfo userInfo, AuthRole authRole,
                                  boolean hasRoleAuth, ProceedingJoinPoint pjp,
                                  @Nullable HttpServletRequest servletRequest) throws DapThrowException {
        Arrays.stream(authRole.customAuth()).forEach(accessClz -> {
            AuthAccessService authAccessService = authAccess.computeIfAbsent(accessClz, clz -> {
                try {
                    return clz.newInstance();
                } catch (InstantiationException | IllegalAccessException e) {
                    log.error("实例化自定义权限业务类{}失败", authRole.customAuth(), e);
                }
                return null;
            });
            if (authAccessService != null) {
                authAccessService.checkAuth(userInfo, authRole, hasRoleAuth, pjp, servletRequest);
            }
        });
    }

}
